package model.world;

import java.util.List;
import java.util.LinkedList;
import util.Coord;

import controller.GameClockListener;

import model.ModelRW;
import model.crafting.RecipeComparer;
import model.entity.Entity;
import model.entity.unit.Player;
import model.inventory.item.Item;
import model.world.tile.Tile;

public class World implements GameClockListener, ModelRW{
	protected List<Chunk> loadedChunks;
	protected List<Chunk> occupiedChunks;
	protected List<Player> playerUnits;
	protected Chunk origin;
	protected boolean gameStarted;
	protected final boolean chunkUnloading = false;
	protected Tile selectedTile;
	protected Entity selectedEntity;
	public static RecipeComparer recipeComparer;

	public World(){
		recipeComparer = new RecipeComparer();
		loadedChunks = new LinkedList<Chunk>();
		occupiedChunks = new LinkedList<Chunk>();
		playerUnits = new LinkedList<Player>();
		gameStarted = false;
	}

	public World(String recipeFile){
		recipeComparer = new RecipeComparer(recipeFile);
		loadedChunks = new LinkedList<Chunk>();
		occupiedChunks = new LinkedList<Chunk>();
		playerUnits = new LinkedList<Player>();
		gameStarted = false;
	}

	private void generateSurroundingChunks(Chunk c){
		for(ChunkDirection cd : ChunkDirection.values()){
			if(c.getNeighbor(cd) == null){
				int direction = cd.toInt();
				long cx, cy;

				if(direction < 4 && direction > 0)
					cx = c.getCX() + 1;
				else if(direction > 4 && direction < 8)
					cx = c.getCX() - 1;
				else
					cx = c.getCX();

				if(direction > 2 && direction < 6)
					cy = c.getCY() + 1;
				else if(direction == 2 || direction == 6)
					cy = c.getCY();
				else
					cy = c.getCY() - 1;

				Chunk neighbor = findChunk(cx, cy);

				if(neighbor == null){
					neighbor = new Chunk(cx, cy);
					loadedChunks.add(neighbor);
				}else if(!loadedChunks.contains(neighbor)){
					loadedChunks.add(neighbor);
				}
			}else{
				if(!loadedChunks.contains(c.getNeighbor(cd)))
					loadedChunks.add(c.getNeighbor(cd));
			}
		}
		connectLoadedChunks();
	}

	protected Coord getChunkCoord(long wx, long wy){
		long cx, cy;
		if(wx < 0){
			cx = (wx + 1)/Chunk.getWidth() - 1;
		}else{
			cx = wx/Chunk.getWidth();
		}
		if(wy < 0){
			cy = (wy + 1)/Chunk.getHeight() - 1;
		}else{
			cy = wy/Chunk.getHeight();
		}
		return new Coord(cx, cy);
	}

	protected Chunk findChunk(long cx, long cy){
		for(Chunk c : loadedChunks){
			if(c.getCX() == cx && c.getCY() == cy)
				return c;
		}
		return null;
	}

	private void findOccupiedChunks(){
		occupiedChunks = new LinkedList<Chunk>();
		for(Player p : playerUnits){
			if(!occupiedChunks.contains(p.getParentChunk())){
				occupiedChunks.add(p.getParentChunk());
			}
		}
	}

	private void loadRequiredChunks() {
		if(chunkUnloading){
			loadedChunks = new LinkedList<Chunk>();
			loadedChunks.add(origin);
			loadedChunks.addAll(occupiedChunks);
		}
		for(Chunk c : occupiedChunks){
			generateSurroundingChunks(c);
		}
	}

	private void connectLoadedChunks() {
		for(Chunk c : loadedChunks){
			for(ChunkDirection cd : ChunkDirection.values()){
				int direction = cd.toInt();
				long cx, cy;

				if(direction < 4 && direction > 0)
					cx = c.getCX() + 1;
				else if(direction > 4 && direction < 8)
					cx = c.getCX() - 1;
				else
					cx = c.getCX();

				if(direction > 2 && direction < 6)
					cy = c.getCY() + 1;
				else if(direction == 2 || direction == 6)
					cy = c.getCY();
				else
					cy = c.getCY() - 1;

				Chunk neighbor = findChunk(cx, cy);
				if(neighbor != null)
					c.setNeighbor(cd, neighbor);
			}
		}
	}

	@Override
	public void tick() {
		if(gameStarted){
			for(Player p : playerUnits){
				p.tick();
			}
			for(Chunk c : loadedChunks){
				c.tick();
			}
			findOccupiedChunks();
			loadRequiredChunks();
			connectLoadedChunks();
		}
	}

	private void generateChunk(long cx, long cy){
		Chunk c = new Chunk(cx, cy);
		loadedChunks.add(c);
		connectLoadedChunks();
	}

	protected Tile getTile(long x, long y){
		long cx, cy;
		long tx, ty;

		if(x < 0){
			cx = (x + 1)/Chunk.getWidth() - 1;
			tx = ((x + Chunk.getWidth())%Chunk.getWidth() + Chunk.getWidth())%Chunk.getWidth();
		}else{
			cx = x/Chunk.getWidth();
			tx = x%Chunk.getWidth();
		}
		if(y < 0){
			cy = (y + 1)/Chunk.getHeight() - 1;
			ty = ((y + Chunk.getHeight())%Chunk.getHeight() + Chunk.getHeight())%Chunk.getHeight();
		}else{
			cy = y/Chunk.getHeight();
			ty = y%Chunk.getWidth();
		}



		Chunk location = findChunk(cx, cy);
		if(location == null)
			return null;
		return location.getTile((int)tx, (int)ty);
	}

	public RecipeComparer getRecipeComparer() {
		return recipeComparer;
	}

	/*-------------------------------------------------------------
	 * ModelRW methods
	 * -------------------------------------------------------------
	 */

	public void newGame(){
		origin = new Chunk(0, 0);
		loadedChunks.add(origin);
		occupiedChunks.add(origin);
		generateSurroundingChunks(origin);
		playerUnits.add(new Player(this, origin.getTile(0, 0)));
		gameStarted = true;
		playerUnits.get(0).getInventory().add(new Item(100, 1));
	}

	public void setSelectedTile(long wx, long wy){
		selectedTile = getTile(wx, wy);
		selectedEntity = null;
	}

	public void setSelectedEntityTarget(long wx, long wy){
		if(selectedIsPlayer()){
			Player player = (Player) selectedEntity;
			player.setTarget(getTile(wx, wy));
		}
	}

	@Override
	public void setSelectedEntity(long x, long y) {
		selectedEntity = getTile(x, y).getOccupyingEntity();
		if(selectedEntity == null)
			selectedEntity = getTile(x, y).getPresentUnit(0);
	}

	@Override
	public void entityWorkTile() {
		if(selectedIsPlayer()){
			Player player = (Player)selectedEntity;
			player.workTile();
		}
	}

	@Override
	public void entityHaltWorking() {
		if(selectedIsPlayer()){
			Player player = (Player)selectedEntity;
			player.stopWorking();
		}
	}

	@Override
	public void setSelectedEntity(String name) {
		if(selectedTile == null)
			return;
		if(selectedTile.getOccupyingEntity() != null && selectedTile.getOccupyingEntity().getGUID().toString().equals(name)){
			selectedEntity = selectedTile.getOccupyingEntity();
			selectedTile = null;
			return;
		}
		for(int i = 0; i < selectedTile.getNumPresentUnits(); i++){
			if(selectedTile.getPresentUnit(i).getGUID().toString().equals(name)){
				selectedEntity = selectedTile.getPresentUnit(i);
				selectedTile = null;
				return;
			}
		}
		return;
	}

	@Override
	public void placeSelectedItem() {
		if(selectedEntity != null && selectedEntity.getInventory().getSelectedItem() != null && selectedEntity.getInventory().getSelectedItem().isPlaceable()){
			if(selectedEntity instanceof Player){
				Player player = (Player)selectedEntity;
				player.placeItem();
			}
		}
	}

	@Override
	public void setSelectedItemID(int itemID) {
		selectedEntity.getInventory().setSelected(itemID);
	}

	@Override
	public void addSelectedItem(long x, long y) {
		selectedEntity.addSelectedItemToCrafting((int)x, (int)y);
	}

	@Override
	public void removeSelectedItem(long x, long y) {
		selectedEntity.removeCraftingItem(x, y);
	}

	/*-------------------------------------------------------------
	 * ModelReader methods
	 * ------------------------------------------------------------
	 */

	public int numLoadedChunks(){
		return loadedChunks.size();
	}

	public int getTileType(long x, long y) {
		Tile t = getTile(x, y);
		if(t == null){
			generateChunk(getChunkCoord(x, y).getX(), getChunkCoord(x, y).getY());
			t = getTile(x, y);
		}
		return t.getResourceType().toInt();
	}

	public int getEntityID(long x, long y) {
		Tile t = getTile(x, y);
		if(t == null)
			return -1;
		else{
			if(t.getOccupyingEntity() != null)
				return t.getOccupyingEntity().getID();
			else if(t.getPresentUnit(0) != null)
				return t.getPresentUnit(0).getID();
			return -1;
		}
	}

	public boolean isSelectedTile(long wx, long wy){
		return getTile(wx, wy) == selectedTile;
	}

	public boolean entitySelected() {
		return selectedEntity != null;
	}

	public int getSelectedEntityID() {
		return selectedEntity.getID();
	}

	@Override
	public boolean selectedIsPlayer() {
		return selectedEntity != null && selectedEntity instanceof Player;
	}

	@Override
	public boolean entityIsWorking() {
		if(selectedEntity == null)
			return false;
		return selectedEntity.isWorking();
	}

	@Override
	public int getRCR() {
		if(selectedEntity == null)
			return -1;
		return selectedEntity.getResourceCollectionRate(selectedEntity.getCurrentTile().getResourceType());
	}

	@Override
	public int getTileType() {
		if(selectedTile == null)
			return -1;
		return selectedTile.getResourceType().toInt();
	}

	@Override
	public long getX() {
		if(selectedTile != null)
			return selectedTile.getX();
		if(selectedEntity != null)
			return selectedEntity.getX();
		return 0;
	}

	@Override
	public long getY() {
		if(selectedTile != null)
			return selectedTile.getY();
		if(selectedEntity != null)
			return selectedEntity.getY();
		return 0;
	}

	@Override
	public int getInventorySize() {
		if(selectedTile != null){
			int workingEntities = selectedTile.getOccupyingEntity() == null ? 0 : 1;
			return selectedTile.getNumPresentUnits() + workingEntities;
		}
		if(selectedEntity != null){
			return selectedEntity.getInventory().size();
		}
		return 0;
	}

	@Override
	public int getInventoryItemID(int i) {
		if(selectedTile != null){
			if(i == 0 && selectedTile.getOccupyingEntity() != null)
				return selectedTile.getOccupyingEntity().getID();
			if(selectedTile.getOccupyingEntity() != null && selectedTile.getPresentUnit(i+1) != null)
				return selectedTile.getPresentUnit(i + 1).getID();
			if(selectedTile.getPresentUnit(i) != null)
				return selectedTile.getPresentUnit(i).getID();
			return -1;
		}
		if(selectedEntity != null){
			if(selectedEntity.getInventory().getItem(i) != null)
				return selectedEntity.getInventory().getItem(i).getID();
			return -1;
		}
		return -1;
	}

	@Override
	public long getInventoryItemQuantity(int i) {
		if(selectedEntity != null && selectedEntity.getInventory().getItem(i) != null)
			return selectedEntity.getInventory().getItem(i).getQuantity();
		return -1;
	}

	@Override
	public String getUnitGUID(int i) {
		if(selectedTile != null){
			if(i == 0 && selectedTile.getOccupyingEntity() != null)
				return selectedTile.getOccupyingEntity().getGUID().toString();
			if(selectedTile.getOccupyingEntity() != null && selectedTile.getPresentUnit(i+1) != null)
				return selectedTile.getPresentUnit(i + 1).getGUID().toString();
			if(selectedTile.getPresentUnit(i) != null)
				return selectedTile.getPresentUnit(i).getGUID().toString();
			return null;
		}
		return null;
	}

	@Override
	public int getTableWidth() {
		if(selectedEntity != null)
			return selectedEntity.getCraftingTable().getWidth();
		return 0;
	}

	@Override
	public int getTableHeight() {
		if(selectedEntity != null)
			return selectedEntity.getCraftingTable().getHeight();
		return 0;
	}

	@Override
	public int getTableID(int x, int y) {
		if(selectedEntity != null && selectedEntity.getCraftingTable().getItem(x, y) != null)
			return selectedEntity.getCraftingTable().getItem(x, y).getID();
		return -1;
	}

	@Override
	public int getOutputID() {
		if(selectedEntity != null && selectedEntity.getCraftingTable().getOutput() != null)
			return selectedEntity.getCraftingTable().getOutput().getID();
		return -1;
	}

	@Override
	public Object getSelectedItem() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean placeableItemSelected() {
		if(selectedEntity == null)
			return false;
		if(selectedEntity.getInventory().getSelectedItem() == null)
			return false;
		return selectedEntity.getInventory().getSelectedItem().isPlaceable();
	}

	@Override
	public void removeCraftingOutput() {
		if(selectedEntity != null){
			if(selectedEntity.getCraftingTable().getOutput() != null){
				selectedEntity.takeCraftingOutput();
			}
		}
	}
}
